﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Daphne.GameShared;

namespace GameEngine.DrawEvaluator
{
    /// <summary>
    /// Basic draw evalautor. Checks draw by repetition and maximum number of moves.
    /// </summary>
    public class DrawEvaluator: IDrawEvaluator
    {
        /// <summary>
        /// Constant of how many repetitions can occur to draw
        /// </summary>
        private const int keyMaxRepetitionCount = 3;


        /// <summary>
        /// Maximum number of moves for English checkers
        /// </summary>
        private const int keyMaximumNumberOfMoves = 40;

        /// <summary>
        /// Access to the gameboard set by GameRuleControllerBuilder
        /// </summary>
        public GameBoard GameBoard
        { get; set; }

        
        /// <summary>
        /// Overridable property for returning maximum number of moves
        /// </summary>
        protected virtual int MaximumNumberOfMoves
        {
            get
            {
                return keyMaximumNumberOfMoves;
            }
        }

        /// <summary>
        /// Provides that move back and checks whether it is silent move for draw of maximum number of moves
        /// </summary>
        protected virtual bool CheckMoveForDraw(Move move)
        {
            // undo first move
            GameBoard.UndoMove(move);
            GameBoard.ChangeOnMove();

            if (move.RemovingFigure == true)
                return false;

            enumFigureType figureType = GameBoard.GetField(move.startPos);
            if (figureType.IsKing() == false)
                return false;

            return true;
        }


        /// <summary>
        /// Checks, if draw has occured by several times accessing the same position
        /// </summary>
        private bool IsDrawByRepetition(List<Move> lstGameHistory)
        {
            if (lstGameHistory.Count() == 0)
                return false;

            // calculate repetitions
            Dictionary<BitBoard, int> repetitions = new Dictionary<BitBoard, int>();
            foreach (Move move in lstGameHistory)
            {
                int count;
                if (repetitions.TryGetValue(move.bitOriginalPosition, out count) == false)
                    repetitions[move.bitOriginalPosition] = 1;
                else
                    repetitions[move.bitOriginalPosition] = count + 1;
            }

            // get max repetition
            int maxRepetition = repetitions.Values.Max();

            return maxRepetition >= keyMaxRepetitionCount ? true : false;
        }

        /// <summary>
        /// Checks draw by maximum number of moves: no man has advanced and no piece has been jumped
        /// </summary>
        private bool IsDrawByMaximumNumberOfMoves(List<Move> lstGameHistory)
        {
            BitBoard bitBoard = GameBoard.PackGameBoard();
            enumFigureColor onMove = GameBoard.OnMove;

            // moves count for each side
            int lDrawMovesWhite = 0;
            int lDrawMovesBlack = 0;

            for (int i = lstGameHistory.Count - 1; i >= 0; i--)
            {
                // undo move by this function and check for king move, not jump, returns true
                if (CheckMoveForDraw(lstGameHistory[i]) == false)
                    break;

                // increment draw moves according to the side on move and undo move
                Move undoMove = lstGameHistory[i];
                enumFigureType figureType = GameBoard.GetField(undoMove.startPos);
                if (figureType != enumFigureType.Nothing)
                {
                    if (figureType.FigureColor() == enumFigureColor.White)
                        lDrawMovesWhite++;
                    else
                        lDrawMovesBlack++;
                } // if
            } // while

            // restore the model
            GameBoard.UnpackGameBoard(bitBoard);
            GameBoard.SetOnMove(onMove);

            return lDrawMovesWhite >= MaximumNumberOfMoves || lDrawMovesBlack >= MaximumNumberOfMoves;
        }

        /// <summary>
        /// Prepares count of figures on the gameboard
        /// </summary>
        protected FigureCounts GetCurrentFigureCounts()
        {
            FigureCounts figCnt = new FigureCounts();
            
            figCnt.WMCnt = GameBoard.CountFigures(enumFigureType.WhiteMan);
            figCnt.BMCnt = GameBoard.CountFigures(enumFigureType.BlackMan);
            figCnt.WKCnt = GameBoard.CountFigures(enumFigureType.WhiteKing);
            figCnt.BKCnt = GameBoard.CountFigures(enumFigureType.BlackKing);

            return figCnt;
        }

        /// <summary>
        /// Checks current situation on GameBoard and returns number of moves if special position occurs
        /// otherwise returns NULL
        /// </summary>
        protected virtual int? IsSpecialPosition()
        {
            return null;
        }

        /// <summary>
        /// Checks special endings where the number of maximum moves is less than ordinary number of moves
        /// It may depend on the number of figures or position, count for each side the maximum
        /// number of moves separataly
        /// </summary>
        private bool IsDrawBySpecialPosition(List<Move> lstGameHistory)
        {
            BitBoard bitBoard = GameBoard.PackGameBoard();
            enumFigureColor onMove = GameBoard.OnMove;

            int lDrawMovesWhite = 0;
            int lDrawMovesBlack = 0;

            for (int i = lstGameHistory.Count - 1; i >= 0; i--)
            {
                // check last board situation as special position
                int? maximumNumberOfMoves = IsSpecialPosition();
                if (maximumNumberOfMoves.HasValue)
                {
                    int max = maximumNumberOfMoves.Value;
                    if (lDrawMovesWhite >= max || lDrawMovesBlack > max)
                    {
                        // restore the model
                        GameBoard.UnpackGameBoard(bitBoard);
                        GameBoard.SetOnMove(onMove);

                        return true;
                    }
                }

                // to the position before this move
                Move undoMove = lstGameHistory[i];
                GameBoard.UndoMove(undoMove);
                GameBoard.ChangeOnMove();

                // increment draw moves according to the side on move and undo move
                enumFigureType figureType = GameBoard.GetField(undoMove.startPos);
                if (figureType != enumFigureType.Nothing)
                {
                    if (figureType.FigureColor() == enumFigureColor.White)
                        lDrawMovesWhite++;
                    else
                        lDrawMovesBlack++;
                } // if
            } // while

            // restore the model
            GameBoard.UnpackGameBoard(bitBoard);
            GameBoard.SetOnMove(onMove);

            return false;
        }


        /// <summary>
        /// Main method for checking draw
        /// </summary>
        /// <param name="lstGameHistory">Moves done on the gameboard</param>
        /// <returns></returns>
        public bool IsDraw(List<Move> lstGameHistory)
        {
            // check draw by repetition
            if (IsDrawByRepetition(lstGameHistory))
                return true;

            // check maximum number of moves
            if (IsDrawByMaximumNumberOfMoves(lstGameHistory))
                return true;

            // check special positions
            if (IsDrawBySpecialPosition(lstGameHistory))
                return true;

            return false;
        }
    }
}
